/*
 *  FILE: vfs_syscall.c
 *  AUTH: mcc | jal
 *  DESC:
 *  DATE: Wed Apr  8 02:46:19 1998
 *  $Id: vfs_syscall.c,v 1.1 2012/10/10 20:06:46 william Exp $
 */

#include "kernel.h"
#include "errno.h"
#include "globals.h"
#include "fs/vfs.h"
#include "fs/file.h"
#include "fs/vnode.h"
#include "fs/vfs_syscall.h"
#include "fs/open.h"
#include "fs/fcntl.h"
#include "fs/lseek.h"
#include "mm/kmalloc.h"
#include "util/string.h"
#include "util/printf.h"
#include "fs/stat.h"
#include "util/debug.h"

/* To read a file:
 *      o fget(fd)
 *      o call its virtual read f_op
 *      o update f_pos
 *      o fput() it
 *      o return the number of bytes read, or an error
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd is not a valid file descriptor or is not open for reading.
 *      o EISDIR
 *        fd refers to a directory.
 *
 * In all cases, be sure you do not leak file refcounts by returning before
 * you fput() a file that you fget()'ed.
 */

static int returnWithCode(file_t* fptr, int error_code)
{
    fput(fptr);
    return error_code;
}


static int notValidFilename(const char *name)
{
        const char* p = NULL;
        for( p = name; *p != '\0'; ++p)
        {
            if(*p == '/')
            {
                dbg_print("the name : %s is not the last resolved name\n", name);
                return 1;
            }
        }
        return 0;
}


int
do_read(int fd, void *buf, size_t nbytes)
{
                if(fd<0||fd>=NFILES)
            return -EBADF;
       /* NOT_YET_IMPLEMENTED("VFS: do_read");*/
        file_t* fptr = fget(fd);

        /*fd is not a valid file descriptor or is not open for reading*/
        if(fptr == NULL)
        {
            return -EBADF;
        }
      
        if (!(fptr->f_mode & FMODE_READ)){
                fput(fptr);
                return -EBADF;
        }
        /*check fptr is a directory*/
        if(S_ISDIR(fptr->f_vnode->vn_mode))
        {
                fput(fptr);
                return -EISDIR;
        }
        
        /*vn_fs->fs_root->vn_ops*/
        int res = fptr->f_vnode->vn_ops->read(fptr->f_vnode, fptr->f_pos, buf, nbytes);
        fptr->f_pos += res;
        fput(fptr);
        return res;
}

/* Very similar to do_read.  Check f_mode to be sure the file is writable.  If
 * f_mode & FMODE_APPEND, do_lseek() to the end of the file, call the write
 * f_op, and fput the file.  As always, be mindful of refcount leaks.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd is not a valid file descriptor or is not open for writing.
 */
int
do_write(int fd, const void *buf, size_t nbytes)
{
            if(fd<0||fd>=NFILES)
            return -EBADF;

        file_t* fptr = fget(fd);

         if(fptr == NULL)
         {
            return -EBADF;
         }
                
        if ((fptr->f_mode & FMODE_WRITE) == 0){
                fput(fptr);
                return -EBADF;
        }



/*        KASSERT( fptr->f_mode == FMODE_WRITE || fptr->f_mode == FMODE_APPEND);*/

        /*change f_pos if necessary*/
        if(fptr->f_mode & FMODE_APPEND)
        {
           do_lseek(fd, 0, SEEK_END);
        }
      /*  dbg_print("%d\n", fptr->f_vnode);
        dbg_print("%d\n", fptr->f_pos);
        dbg_print("%d\n", buf);*/
        int res = fptr->f_vnode->vn_ops->write(fptr->f_vnode, fptr->f_pos, buf, nbytes);
        if(res!=0)
                     KASSERT((S_ISCHR(fptr->f_vnode->vn_mode)) ||
                                (S_ISBLK(fptr->f_vnode->vn_mode)) ||
                                ((S_ISREG(fptr->f_vnode->vn_mode)) && (fptr->f_pos <= fptr->f_vnode->vn_len)));
        fptr->f_pos += res;
        fput(fptr);
        return res;
}

/*
 * Zero curproc->p_files[fd], and fput() the file. Return 0 on success
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd isn't a valid open file descriptor.
 */
int
do_close(int fd)
{
        if(fd<0||fd>=NFILES)
            return -EBADF;
        file_t* fptr =  curproc->p_files[fd];
         if(fptr == NULL)
                return -EBADF;

        curproc->p_files[fd] = NULL;
        /*NOT_YET_IMPLEMENTED("VFS: do_close");*/
        return returnWithCode(fptr,0);
}

/* To dup a file:
 *      o fget(fd) to up fd's refcount
 *      o get_empty_fd()
 *      o point the new fd to the same file_t* as the given fd
 *      o return the new file descriptor
 *
 * Don't fput() the fd unless something goes wrong.  Since we are creating
 * another reference to the file_t*, we want to up the refcount.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd isn't an open file descriptor.
 *      o EMFILE
 *        The process already has the maximum number of file descriptors open
 *        and tried to open a new one.
 */
int
do_dup(int fd)
{
            if(fd<0||fd>=NFILES)
            return -EBADF;

        file_t* fptr = fget(fd);

         if(fptr == NULL)
                return -EBADF;

        int new_fd = get_empty_fd(curproc);
        if(-EMFILE == new_fd)
            return returnWithCode(fptr, -EMFILE);

        curproc->p_files[new_fd] = fptr;
        return new_fd;
}

/* Same as do_dup, but insted of using get_empty_fd() to get the new fd,
 * they give it to us in 'nfd'.  If nfd is in use (and not the same as ofd)
 * do_close() it first.  Then return the new file descriptor.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        ofd isn't an open file descriptor, or nfd is out of the allowed
 *        range for file descriptors.
 */
int
do_dup2(int ofd, int nfd)
{
/*        NOT_YET_IMPLEMENTED("VFS: do_dup2");*/
        if(ofd<0||ofd>=NFILES||nfd<0||nfd>=NFILES)
            return -EBADF;
        file_t* fptr = fget(ofd);

        if(fptr==NULL)
            return -EBADF;
        if(nfd==ofd)
        {
           return returnWithCode(fptr, nfd); 
        }

        if(!curproc->p_files[nfd])
        {
            do_close(nfd);
        }
        curproc->p_files[nfd] = fptr;

        return nfd;
}

/*
 * This routine creates a special file of the type specified by 'mode' at
 * the location specified by 'path'. 'mode' should be one of S_IFCHR or
 * S_IFBLK (you might note that mknod(2) normally allows one to create
 * regular files as well-- for simplicity this is not the case in Weenix).
 * 'devid', as you might expect, is the device identifier of the device
 * that the new special file should represent.
 *
 * You might use a combination of dir_namev, lookup, and the fs-specific
 * mknod (that is, the containing directory's 'mknod' vnode operation).
 * Return the result of the fs-specific mknod, or an error.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EINVAL
 *        mode requested creation of something other than a device special
 *        file.
 *      o EEXIST
 *        path already exists.
 *      o ENOENT
 *        A directory component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_mknod(const char *path, int mode, unsigned devid)
{

        if (mode != S_IFCHR && mode != S_IFBLK)
                return -EINVAL;
        vnode_t *dir = NULL;
        size_t namelen;
        const char* name = NULL;
        int return_val = dir_namev(path, &namelen, &name, NULL, &dir);
        if (return_val != 0)
                return return_val;
        /*check if path already exists*/
        vnode_t *vn = NULL;
        if (lookup(dir, name, namelen, &vn) == 0){
                vput(dir);
                vput(vn);
                return -EEXIST;
        }
            KASSERT(NULL != dir->vn_ops->mknod);
        dir->vn_ops->mknod(dir, name, namelen, mode, devid);
        vput(dir);
        return 0;
}

/* Use dir_namev() to find the vnode of the dir we want to make the new
 * directory in.  Then use lookup() to make sure it doesn't already exist.
 * Finally call the dir's mkdir vn_ops. Return what it returns.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EEXIST
 *        path already exists.
 *      o ENOENT
 *        A directory component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_mkdir(const char *path)
{
        vnode_t *dir = NULL;
        size_t nameSize = 0;
        const char* name = NULL;
        int return_val = dir_namev(path, &nameSize, &name, NULL, &dir);
        if (return_val != 0)
                return return_val;

        vnode_t *vn = NULL;
        if (lookup(dir, name, nameSize, &vn) == 0){
                vput(dir);
                vput(vn);
                return -EEXIST;
        }
        KASSERT(NULL != dir->vn_ops->mkdir);
        dir->vn_ops->mkdir(dir, name, nameSize);
        dbg_print("new dir!!! dir = %d, name = %s !!!\n", (int)dir, name);
        vput(dir);

        return 0;
}



/* Use dir_namev() to find the vnode of the directory containing the dir to be
 * removed. Then call the containing dir's rmdir v_op.  The rmdir v_op will
 * return an error if the dir to be removed does not exist or is not empty, so
 * you don't need to worry about that here. Return the value of the v_op,
 * or an error.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EINVAL
 *        path has "." as its final component.
 *      o ENOTEMPTY
 *        path has ".." as its final component.
 *      o ENOENT
 *        A directory component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */

int
do_rmdir(const char *path)
{
     /*   NOT_YET_IMPLEMENTED("VFS: do_rmdir");*/
        const char* name = NULL;
        size_t namelen;
        vnode_t * parent = NULL;

        int return_val = dir_namev(path, &namelen, &name, NULL, &parent);

        if(return_val!=0)
            return return_val;
        KASSERT(parent != NULL && name != NULL);
        if(namelen== 1 && name[0]=='.'){
            vput(parent);
            return -EINVAL;
        }

        if(namelen== 2 && name[0]=='.'&&name[1]=='.'){
            vput(parent);
            return -ENOTEMPTY;
        }

        KASSERT(NULL !=  parent->vn_ops->rmdir);
        int res = parent->vn_ops->rmdir(parent, name, namelen);
        vput(parent);

        return res;
}

/*
 * Same as do_rmdir, but for files.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EISDIR
 *        path refers to a directory.
 *      o ENOENT
 *        A component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_unlink(const char *path)
{
       /* NOT_YET_IMPLEMENTED("VFS: do_unlink");*/
        const char* name = NULL;
        size_t namelen;
        vnode_t * parent = NULL;

        int return_val = dir_namev(path, &namelen, &name, NULL, &parent);

        if(return_val!=0)
            return return_val;
        KASSERT(parent != NULL && name != NULL);
        if(namelen== 1 && name[0]=='.'){
              vput(parent);
            return -EINVAL;
        }

        if(namelen== 2 && name[0]=='.'&&name[1]=='.'){
              vput(parent);
            return -ENOTEMPTY;
        }

        vnode_t *vn = NULL;
        int t = lookup(parent, name, namelen, &vn);
        if(t!=0)
        {
            vput(parent);
            return -ENOENT;
        }
        if(S_ISDIR(vn->vn_mode)){
            vput(vn);
            vput(parent);
            return -EISDIR;
        }
        KASSERT(NULL != parent->vn_ops->unlink);
        int res = parent->vn_ops->unlink(parent, name, namelen);
        vput(vn);
        vput(parent);
        return res;
}

/* To link:
 *      o open_namev(from)
 *      o dir_namev(to)
 *      o call the destination dir's (to) link vn_ops.
 *      o return the result of link, or an error
 *
 * Remember to vput the vnodes returned from open_namev and dir_namev.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EEXIST
 *        to already exists.
 *      o ENOENT
 *        A directory component in from or to does not exist.
 *      o ENOTDIR
 *        A component used as a directory in from or to is not, in fact, a
 *        directory.
 *      o ENAMETOOLONG
 *        A component of from or to was too long.
 */

int
do_link(const char *from, const char *to)
{
       /* NOT_YET_IMPLEMENTED("VFS: do_link");*/
    vnode_t* s = NULL;
    int return_val = open_namev(from, O_RDWR , &s, NULL);
    if(return_val!=0)
        return return_val;

    vnode_t* d = NULL;
    const char* name = NULL;
    size_t namelen;
    return_val = dir_namev(to, &namelen, &name, NULL, &d);
    if(return_val!=0)
    {
        vput(s);
        return return_val;
    }

    int res = d->vn_ops->link(s,d,name,namelen);
        vput(d);
        vput(s);
    return res;
}

/*      o link newname to oldname
 *      o unlink oldname
 *      o return the value of unlink, or an error
 *
 * Note that this does not provide the same behavior as the
 * Linux system call (if unlink fails then two links to the
 * file could exist).
 */
int
do_rename(const char *oldname, const char *newname)
{
       /* NOT_YET_IMPLEMENTED("VFS: do_rename");*/
        int return_val = do_link(oldname,newname);
        if(return_val!=0)
            return return_val;
        int res = do_unlink(oldname);
        return res;
}

/* Make the named directory the current process's cwd (current working
 * directory).  Don't forget to down the refcount to the old cwd (vput()) and
 * up the refcount to the new cwd (open_namev() or vget()). Return 0 on
 * success.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o ENOENT
 *        path does not exist.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 *      o ENOTDIR
 *        A component of path is not a directory.
 */
int
do_chdir(const char *path)
{
        vnode_t *oldCwd = curproc->p_cwd;
        vnode_t *newCwd = NULL;
        int return_val = open_namev(path, O_RDWR, &newCwd, NULL);
        if (return_val != 0)
                return return_val;
            if(!S_ISDIR(newCwd->vn_mode))
            {
                vput(newCwd);
                return -ENOTDIR;
            }
        curproc->p_cwd = newCwd;
        vput(oldCwd);
        return 0;
}

/* Call the readdir f_op on the given fd, filling in the given dirent_t*.
 * If the readdir f_op is successful, it will return a positive value which
 * is the number of bytes copied to the dirent_t.  You need to increment the
 * file_t's f_pos by this amount.  As always, be aware of refcounts, check
 * the return value of the fget and the virtual function, and be sure the
 * virtual function exists (is not null) before calling it.
 *
 * Return either 0 or sizeof(dirent_t), or -errno.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        Invalid file descriptor fd.
 *      o ENOTDIR
 *        File descriptor does not refer to a directory.
 */
int
do_getdent(int fd, struct dirent *dirp)
{
            if(fd<0||fd>=NFILES)
            return -EBADF;
        file_t *fptr = NULL;
        fptr = fget(fd);
        if(fptr==NULL)
        {
            return -EBADF;
        }

        if(!S_ISDIR(fptr->f_vnode->vn_mode))
        {
                fput(fptr);
                return -ENOTDIR;
        }

        int len = fptr->f_vnode->vn_ops->readdir(fptr->f_vnode, fptr->f_pos, dirp);

        fptr->f_pos = fptr->f_pos + len;

        fput(fptr);

       if(len <=0)
            return 0;
        /*NOT_YET_IMPLEMENTED("VFS: do_getdent");*/
        return sizeof(*dirp);
}

/*
 * Modify f_pos according to offset and whence.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd is not an open file descriptor.
 *      o EINVAL
 *        whence is not one of SEEK_SET, SEEK_CUR, SEEK_END; or the resulting
 *        file offset would be negative.
 */
int
do_lseek(int fd, int offset, int whence)
{
            if (fd < 0 || fd >= NFILES)
                return -EBADF;

        if( whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END)
        {
            return -EINVAL;
        }

        file_t* fptr = fget(fd);

         if(fptr == NULL)
                return -EBADF;

        int new_offset = whence == SEEK_SET ? offset:
                                    (whence == SEEK_CUR ? fptr->f_pos + offset: fptr->f_vnode->vn_len + offset);

        if( new_offset < 0)
        {
             fput(fptr);
             return -EINVAL;
        }

        fptr->f_pos = new_offset;
        fput(fptr);
        return new_offset;
}


/*
 * Find the vnode associated with the path, and call the stat() vnode operation.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o ENOENT
 *        A component of path does not exist.
 *      o ENOTDIR
 *        A component of the path prefix of path is not a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */

int
do_stat(const char *path, struct stat *buf)
{
        vnode_t *vn = NULL;
        int return_val = open_namev(path, O_RDWR, &vn, NULL);
        if (return_val != 0)
                return return_val;
        KASSERT(vn->vn_ops->stat);
        return_val = vn->vn_ops->stat(vn, buf);
        vput(vn);
        return return_val;
}

#ifdef __MOUNTING__
/*
 * Implementing this function is not required and strongly discouraged unless
 * you are absolutely sure your Weenix is perfect.
 *
 * This is the syscall entry point into vfs for mounting. You will need to
 * create the fs_t struct and populate its fs_dev and fs_type fields before
 * calling vfs's mountfunc(). mountfunc() will use the fields you populated
 * in order to determine which underlying filesystem's mount function should
 * be run, then it will finish setting up the fs_t struct. At this point you
 * have a fully functioning file system, however it is not mounted on the
 * virtual file system, you will need to call vfs_mount to do this.
 *
 * There are lots of things which can go wrong here. Make sure you have good
 * error handling. Remember the fs_dev and fs_type buffers have limited size
 * so you should not write arbitrary length strings to them.
 */
int
do_mount(const char *source, const char *target, const char *type)
{
        NOT_YET_IMPLEMENTED("MOUNTING: do_mount");
        return -EINVAL;
}

/*
 * Implementing this function is not required and strongly discouraged unless
 * you are absolutley sure your Weenix is perfect.
 *
 * This function delegates all of the real work to vfs_umount. You should not worry
 * about freeing the fs_t struct here, that is done in vfs_umount. All this function
 * does is figure out which file system to pass to vfs_umount and do good error
 * checking.
 */
int
do_umount(const char *target)
{
        NOT_YET_IMPLEMENTED("MOUNTING: do_umount");
        return -EINVAL;
}
#endif
